package evolutionaryLoop;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import statistics.PopulationStatistics;
import evolutionaryLoop.selectionMechanisme.SelectionMechanism;
import evolutionaryProblems.Phenotype;

public class Population implements Iterable<Phenotype>{

	private List<Phenotype> populationPool;

	public Population(List<Phenotype> populationPool) {
		this.populationPool = populationPool;
	} 
	
	public Population(int howMany) {
		populationPool = new ArrayList<Phenotype>(howMany); 
	}
	
	private void insert(Phenotype phenotype){
		populationPool.add(phenotype); 
	}
	
	@SuppressWarnings("unused")
	private void remove(Phenotype phenotype){
		populationPool.remove(phenotype); 
	}
	
	public int size(){
		return populationPool.size(); 
	}
	

	public void scaleToSize(int size){
		populationPool = getBest(size).getPopulationList(); 
	}
	
	public void cull(double culling){
		scaleToSize((int)((1-culling) * size())); 
	}
	
	public Population getRandomPopulation(int howMany){
		Population randomPopulation = new Population(howMany); 
		for (int i = 0; i < howMany; i ++) {
			int index = (int) Math.floor(Math.random()*populationPool.size()); 
			randomPopulation.insert(populationPool.get(index)); 
		}
		return randomPopulation;
	}
	
	public Population getBest(int howMany){
		Population bestPopulation = new Population(howMany);
		
		boolean haveSorted = false; 
		while (!haveSorted) {
			try {
				Collections.sort(populationPool);
				haveSorted = true; 
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} 
			
		}
		for (int i = populationPool.size()-1; i > populationPool.size()-howMany-1; i--){
			bestPopulation.insert(populationPool.get(i)); 
		}
		return bestPopulation; 
	}
	
	public Phenotype getBest(){
		Phenotype best = populationPool.get(0); 
		for (Phenotype phenotype : populationPool){
			if (phenotype.compareTo(best)>0){
				best = phenotype; 
			}
		}
		return best; 
	}
	
	public List<Phenotype> getPopulationList(){
		Population populationCopy = new Population(size()); 
		for (Phenotype phenotype : populationPool){
			populationCopy.insert(phenotype);
		}
		return populationCopy.populationPool; 
	}
	
	public void merge(Population otherPopulation){
		populationPool.addAll(otherPopulation.getPopulationList()); 
	}

	@Override
	public Iterator<Phenotype> iterator() {
		return populationPool.iterator();
	}

	public boolean isSolved() {
//		System.out.println(getBest());
		return getBest().isSolution(); 
	}

	public Population nextGeneration(EvolutionaryParameters parameters) {
		int howMany = parameters.getNumOfChildenToBeGenerated(); 
		int crossoverPoints = parameters.getCrossoverPoints();
		double crossoverRate = parameters.getCrossoverRate(); 
		double mutationRate = parameters.getMutationRate(); 
		SelectionMechanism selectionMachanism = parameters.getParenetSelectionMechanism();
		selectionMachanism.setPopulation(this); 
		List<Phenotype> parents = selectParents(selectionMachanism, howMany);
		List<Phenotype> nextGeneration = new ArrayList<Phenotype>(howMany); 
		
		Phenotype pheno1 = null; 
		Phenotype pheno2 = null; 
		for (int i = 0; i < parents.size(); i+=2){
			//Handles the case of an odd number of parents being selected
			if (! (i+1 < howMany)) {
				pheno1 = parents.get(i);
				pheno1 = pheno1.mutate(); 
				nextGeneration.add(pheno1);
				break; 
			}
			
			pheno1 = parents.get(i); 
			pheno2 = parents.get(i+1); 
			if (Math.random() < crossoverRate) {
				Collection<Phenotype> children = pheno1.crossover(pheno2, crossoverPoints); 
				children = mutatePhenotypes(children, mutationRate);
				nextGeneration.addAll(children); 
			} else {
				pheno1 = parents.get(i);
				pheno1 = pheno1.mutate(); 
				nextGeneration.add(pheno1);
				
				pheno2 = parents.get(i+1);
				pheno2 = pheno2.mutate(); 
				nextGeneration.add(pheno2);
			}
		}
		
		return new Population(nextGeneration);
	}
	
	private List<Phenotype> selectParents(SelectionMechanism selectionMechanism, int howMany){
		List<Phenotype> selected = new ArrayList<Phenotype>(howMany); 
		for (int i = 0; i < howMany; i++) {
			selected.add(selectionMechanism.select()); 
		}
		return selected; 
	}


	private Collection<Phenotype> mutatePhenotypes(Collection<Phenotype> phenotypeList, double mutationRate) {
		Collection<Phenotype> mutatedPhenotypeList = new ArrayList<Phenotype>(phenotypeList.size());
		for (Phenotype phenotype : phenotypeList) {
			mutatedPhenotypeList.add(phenotype.mutate()); 
		}
		return mutatedPhenotypeList;
	}

//	private Population selectParents(SelectionMechanism selectionMechanisme, int howMany) {
//		Population parents = new Population(howMany);
//		for (int i = 0; i < howMany; i++) {
//			parents.insert(selectionMechanisme.select()); 
//		}
//		return parents;
//	}

	public Phenotype getRandom() {
		return populationPool.get((int) Math.floor(Math.random()*populationPool.size()));
	}
	
	public PopulationStatistics getStatistics(){
		double avgFitness = getAvgFitness(); 
		double standardDiviation = getStandardDiviation(); 
		Phenotype worstFitness = getWorstPhenotype(); 
		Phenotype bestFiness = getBestPhenotype(); 
		return new PopulationStatistics(bestFiness, worstFitness, avgFitness, standardDiviation); 
	}
	
	private double getAvgFitness(){
		double totalFitness = 0; 
		for (Phenotype phenotype : this) {
			totalFitness += phenotype.getFitness(); 
		}
		return totalFitness/size(); 
	}
	
	private Phenotype getWorstPhenotype(){
		Phenotype worstFitness = this.populationPool.get(0); 
		for (Phenotype phenotype : this) {
//			System.out.println(phenotype.compareTo(worstFitness));
			if (phenotype.compareTo(worstFitness) < 0) {
				worstFitness = phenotype; 
			}
		}
		return worstFitness; 
	}
	
	private Phenotype getBestPhenotype(){
		Phenotype bestFitness = this.populationPool.get(0); 
		for (Phenotype phenotype : this) {
			if (phenotype.compareTo(bestFitness) > 0) {
				bestFitness = phenotype; 
			}
		}
		return bestFitness; 
	}
	
	private double getStandardDiviation() {
		double totalStandardDiviation = 0; 
		double avgFitness = getAvgFitness(); 
		for (Phenotype phenotyp : this) {
			totalStandardDiviation += Math.pow(phenotyp.getFitness()-avgFitness, 2); 
		}
		return Math.sqrt(totalStandardDiviation/size()); 
	}
	
	public String toString(){
		String theString = ""; 
		for (Phenotype phenotype : this) {
			theString += phenotype.toString() + "\n";  
		}
		return theString; 
	}
	
}
